from collections import defaultdict
from dataclasses import dataclass, field
from functools import lru_cache
from typing import Dict, List

from flask import current_app as app

from app.models.roleModel import METHOD_TO_FLAG
from app.role_control import DefaultRuleControl
from configs.settings import RULE_WHITE_LIST
from core import ExtBlueprint, ExtRule
from utils.crypto import Crypt


@dataclass
class BlueNode:
    name: str
    blue: ExtBlueprint
    rules: List[ExtRule] = field(default_factory=list)
    children: list = field(default_factory=list)

    def to_dict(self):
        if self.blue is None:
            blue_name = self.name
        else:
            blue_name = f"{self.blue.role_control.title or self.blue.real_name} || {self.blue.real_name}"

        rules = [
            f"{rule.role_control.title if rule.role_control and rule.role_control.title else rule.endpoint} || {rule.rule} || {rule.endpoint}"  # noqa
            for rule in self.rules
        ]

        result = {
            "blue_name": blue_name,
            "blue_desc": self.blue.role_control.description if self.blue else "",
            "is_menu": self.blue.role_control.is_menu if self.blue else False,
            "rules": rules,
            "children": [child.to_dict() for child in self.children],
        }

        return result

    def to_resources_dict(self, resources_map, disabled_node_ids=None, parent_id=None):
        """
        :param resources_map: {blueprint.real_name/rule.endpoint: Permission}
        :param disabled_node_ids: List[int] 所有锁定不可编辑的权限
        :param parent_id: String 父级权限ID
        """
        if disabled_node_ids is None:
            disabled_node_ids = set()
        if self.blue is None:
            node_name = self.name
            node_desc = ""
            node_path = ""
            is_menu = False
            node_id = None
            disabled = False
        else:
            if not self.blue.role_control.show_node:
                return
            node_resources = resources_map[(self.blue.real_name, "")]
            node_name = f"{self.blue.role_control.title or self.blue.real_name}"
            node_desc = self.blue.role_control.description if self.blue else ""
            node_path = ""
            is_menu = self.blue.role_control.is_menu if self.blue else False
            node_id = f"{Crypt.encrypt(str(node_resources.id))}"
            disabled = any(
                [
                    self.blue.role_control.lock is True,
                    node_resources.id in disabled_node_ids,
                ]
            )

        # 标记是否可编辑（前端组件适配）
        def bind_disabled(rule):
            rule_resources = resources_map[(rule.endpoint, rule.rule)]
            if any([rule_resources.allow_no_login, rule.rule in RULE_WHITE_LIST]):
                return True
            if any(
                [
                    rule_resources.id in disabled_node_ids,
                    rule.role_control and rule.role_control.lock is True,
                ]
            ):
                return True
            return False

        # 将rule信息构建成节点信息
        def build_rule_node_info(_rule: ExtRule, _parent_id):
            rule_resources = resources_map.get((_rule.endpoint, _rule.rule), None)
            if rule_resources is None:
                return None
            rule_db_id = str(rule_resources.id)
            is_allow_no_login = bool(rule_resources.allow_no_login)

            if _rule.role_control is False:
                _node = {
                    "node_name": _rule.endpoint,
                    "node_desc": "",
                    "node_id": f"{Crypt.encrypt(rule_db_id)}",
                    "node_path": f"{rule_resources.path}",
                    "is_menu": False,
                    "node_type": "url",
                    "disabled": True,
                    "is_allow_no_login": is_allow_no_login,
                    "parent_id": _parent_id,
                    "children": [],
                }
            else:
                if not _rule.role_control.show_node:
                    return
                _node = {
                    "node_name": _rule.role_control.title,
                    "node_desc": _rule.role_control.description,
                    "node_id": f"{Crypt.encrypt(rule_db_id)}",
                    "node_path": f"{rule_resources.path}",
                    "is_menu": False,
                    "node_type": "url",
                    "disabled": bind_disabled(_rule),
                    "is_allow_no_login": is_allow_no_login,
                    "parent_id": _parent_id,
                    "children": [],
                }
                for method in _rule.methods:
                    if method not in METHOD_TO_FLAG:
                        continue
                    method_control: DefaultRuleControl = getattr(
                        _rule.role_control, method, None
                    )
                    ext_id = str(METHOD_TO_FLAG[method].value)
                    if method_control is None:
                        n_name, n_desc = METHOD_TO_FLAG[method].label, ""
                    else:
                        n_name, n_desc = (
                            method_control.title,
                            method_control.description,
                        )
                    _node["children"].append(
                        {
                            "node_name": n_name,
                            "node_desc": n_desc,
                            "node_id": f"{Crypt.encrypt(rule_db_id + ':' + ext_id)}",
                            "node_path": f"{rule_resources.path}",
                            "is_menu": False,
                            "node_type": "method",
                            "is_model": False,
                            "disabled": _node["disabled"],
                            "is_allow_no_login": is_allow_no_login,
                            "parent_id": _node["node_id"],
                            "children": [],
                        }
                    )

            return _node

        current_rules: List[ExtRule] = self.rules

        rules = []
        for rule in current_rules:
            _rule = build_rule_node_info(rule, node_id)
            if _rule is not None:  # 消除show_node控制参数副作用
                rules.append(_rule)

        children = []
        for child in self.children:
            _child = child.to_resources_dict(
                resources_map, disabled_node_ids=disabled_node_ids, parent_id=node_id
            )
            if _child is not None:  # 消除show_node控制参数副作用
                children.append(_child)
        children.extend(rules)

        result = {
            "node_name": node_name,
            "node_desc": node_desc,
            "node_id": node_id,
            "node_path": node_path,
            "is_menu": is_menu,
            "node_type": "model",
            "disabled": disabled,
            "parent_id": parent_id,
            "children": children,
        }

        return result


@lru_cache(maxsize=None)
def get_rule_tree() -> BlueNode:
    """获取权限路由树，由项目初始权限命令生成，静态数据，可无限缓存"""
    adp_rule_list: List[ExtRule] = app.url_map.iter_rules()
    adp_blueprints: Dict[ExtBlueprint] = app.blueprints

    # 得到蓝图与规则的映射，同时获取一级rule和白名单路由
    root_rule_list = []
    bp_rule_map = defaultdict(list)
    for item in adp_rule_list:
        # if item.parent_blueprint_uuid is not None and item.rule not in RULE_WHITE_LIST:
        #     bp_rule_map[item.parent_blueprint_uuid].append(item)
        # else:
        #     root_rule_list.append(item)
        # 除顶级路由，保留原始层级
        if item.parent_blueprint_uuid is None:
            root_rule_list.append(item)
        else:
            bp_rule_map[item.parent_blueprint_uuid].append(item)

    # 构建蓝图路由层级树
    tree = []
    bp_list = list(adp_blueprints.keys())

    def build_blue_node(current_bp: ExtBlueprint):
        node = BlueNode(current_bp.real_name, current_bp, bp_rule_map[current_bp.uuid])
        for child_bp, _ in current_bp._blueprints:
            child: ExtBlueprint = child_bp  # 类型注解，消除pycharm的警告
            node.children.append(build_blue_node(child))
            bp_list.remove(child.real_name)
        return node

    for bp_name in bp_list:
        if bp_name in ("restx_doc",):  # noqa
            continue
        bp: ExtBlueprint = adp_blueprints[bp_name]
        tree.append(build_blue_node(bp))

    node_tree = BlueNode("root", None, root_rule_list, tree)
    return node_tree
